﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.Windows.Forms;
using System.Diagnostics;

namespace GShare1._0.Server
{
    public delegate void MainThreadInvoker(Delegate MI);
    public delegate void MessageBoxInvoker(string msg);
    public delegate void ClientListUpdateDelegate(List<Client> clientList);
    public class Client
    {
        public Socket _clientSocket;
        public byte[] Buffer;
        private MessageBoxInvoker _invoker;
        private Download download;
        public AutoResetEvent autoEvent;
        public ClientInfo clientInfo;
        private Server server;
        private MessageRelayDelegate relayInvoker;

        public Client(Socket _socket, MessageBoxInvoker invoker,ClientInfo CI,Server server,MessageRelayDelegate relayInvoker)
        {
            autoEvent = new AutoResetEvent(false);
            _clientSocket = _socket;
            Buffer = new byte[_clientSocket.ReceiveBufferSize];
            _invoker = invoker;
            Debug.WriteLine("Created Client");
            clientInfo = CI;
            this.server = server;
            this.relayInvoker = relayInvoker;
        }



        internal void ReceiveCallback(IAsyncResult ar)
        {
            try
            {
                int dataRead = _clientSocket.EndReceive(ar);

                if (dataRead == 0)
                {
                    Disconnect();
                    return;
                }
                //resize buffer to amount received
                Array.Resize(ref Buffer, dataRead);

                //write buffer to string
                string msg = Encoding.ASCII.GetString(Buffer);


                if (!string.IsNullOrEmpty(msg))
                {
                    switch (msg)
                    {
                        case "SENDFILE":
                            //MessageBox.Show("Download Starting");
                            GUI_FileDownload downloadForm = new GUI_FileDownload();
                            download = new Download(this,downloadForm);
                            download.BeginConnection();

                            //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
                            
                            //Wait till download done or error
                            autoEvent.WaitOne();
                            downloadForm.Close();
                            break;
                        case "TEXT":
                            TextReader();
                            break;
                        default:
                            //Send Error Code to the Client
                            MessageSender.SendText("COMMANDERROR", this._clientSocket);
                            break;
                    }
                }

                //Loop the Receive

                Buffer = new byte[_clientSocket.ReceiveBufferSize];

                try
                {
                    _clientSocket.BeginReceive(Buffer, 0, _clientSocket.ReceiveBufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), null);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }
            }
            catch (Exception ex)
            {
                //No Propper way to handle this problem
                //http://stackoverflow.com/questions/4662553/how-to-abort-sockets-beginreceive
                //"To cancel a pending call to the BeginConnect() method, close the Socket. 
                //When the Close() method is called while an asynchronous operation is in progress, 
                //the callback provided to the BeginConnect() method is called. A subsequent
                //call to the EndConnect(IAsyncResult) method will throw an ObjectDisposedException to
                //indicate that the operation has been cancelled."
                if (ex.GetType() != typeof(ObjectDisposedException))
                {
                    MessageBox.Show(ex.Message);
                }
            }
        }

        internal void Disconnect()
        {
            _invoker.BeginInvoke(clientInfo.UserName +" Disconnected", null, null);
            relayInvoker.BeginInvoke(clientInfo.UserName + " Disconnected", null, null);
            _clientSocket.Shutdown(SocketShutdown.Both);
            //_clientSocket.Dispose();
            _clientSocket.Close();
            server.clientList.Remove(this);
            server.updateListInvoker.BeginInvoke(server.clientList, null, null);
        }

        internal void TextReader()
        {
            byte[] buffer = new byte[_clientSocket.ReceiveBufferSize];
            int received = _clientSocket.Receive(buffer);

            Array.Resize(ref buffer, received);
            string msg = Encoding.ASCII.GetString(buffer);

            if (!string.IsNullOrEmpty(msg))
            {
                _invoker.BeginInvoke(clientInfo.UserName + ": " + msg, null, null);
                relayInvoker.BeginInvoke(clientInfo.UserName + ": " + msg, null, null);
            }
        }

    }
}
